package com.gitee.hljdrl.dsbridge;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.ffsky.logger.L;
import com.gitee.hljdrl.dsbridge.basis.OnWindowListener;
import com.gitee.hljdrl.dsbridge.basis.WebViewEvent;
import com.gitee.hljdrl.dsbridge.client.DSWebChromeClient;
import com.gitee.hljdrl.dsbridge.jsapi.DSChannel;
import com.gitee.hljdrl.dsbridge.jsapi.DSExecute;
import com.gitee.hljdrl.dsbridge.jsapi.DSPlugin;
import com.gitee.hljdrl.dsbridge.jsapi.DSPluginProvider;
import com.gitee.hljdrl.dsbridge.util.DSSettingUtil;

import java.util.Collection;
import java.util.Map;
import java.util.Set;


public class DSWebView extends WebView implements WebViewEvent {

    private static boolean DEBUG = false;

    public DSWebView(@NonNull Context context) {
        this(context, null);
    }

    public DSWebView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DSWebView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public DSWebView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    /**
     * Set debug mode. if in debug mode, some errors will be prompted by a dialog
     * and the exception caused by the native handlers will not be captured.
     */
    public static void setWebContentsDebuggingEnabled(boolean enabled) {
        WebView.setWebContentsDebuggingEnabled(enabled);
        DEBUG = enabled;
    }

    protected String TAG = "DSWebView";
    //-------------------------------------------------------------------------------------
    private volatile boolean alertBoxBlock = true;
    private OnWindowListener onWindowListener = null;

    protected final Handler mMainHandler = new Handler(Looper.getMainLooper());

    protected final Map<String, DSPlugin> mDSPluginMap = new ArrayMap<>();

    protected DSChannel mDSChannel;
    protected DSExecute mDSExecute;


    @SuppressLint("SetJavaScriptEnabled")
    private void init() {
        L.i(TAG, "init");
        DSSettingUtil.settings(this);
        setWebChromeClient(new DSWebChromeClient(null, this));
        //注册-核心内部通讯桥
        internalCreatePlugin();
        //注册业务层插件
        onCreateDSPlugin();
    }

    @Override
    public void createDSChannel() {
        if (mDSChannel == null) {
            mDSChannel = new DSChannel();
            mDSChannel.setWebViewEvent(this);
        }
    }

    @Override
    public void createDSExecute() {
        if (mDSExecute == null) {
            mDSExecute = new DSExecute(this);
            mDSExecute.setWebViewEvent(this);
        }
    }

    @Override
    public DSExecute getDSExecute() {
        return mDSExecute;
    }

    public DSChannel getDSChannel() {
        return mDSChannel;
    }

    protected void onLoadUrlBefore(String url) {
        Collection<DSPlugin> list = mDSPluginMap.values();
        for (DSPlugin dsPlugin : list) {
            dsPlugin.onLoadUrlBefore(url);
        }
    }

    @Override
    public void setWebChromeClient(@Nullable WebChromeClient client) {
        if (client instanceof DSWebChromeClient) {
            DSWebChromeClient dsWebChromeClient = (DSWebChromeClient) client;
            dsWebChromeClient.setWebViewEvent(this);
        }
        super.setWebChromeClient(client);
    }

    @Override
    public void loadUrl(final String url) {
        L.i(TAG,"loadUrl-->",url);
        runOnMainThread(new Runnable() {
            @Override
            public void run() {
                if (url != null && url.startsWith("javascript:")) {
                    DSWebView.super.loadUrl(url);
                } else {
                    onLoadUrlBefore(url);
                    DSWebView.super.loadUrl(url);
                }
            }
        });
    }

    @Override
    public void loadUrl(final String url, final Map<String, String> additionalHttpHeaders) {
        runOnMainThread(new Runnable() {
            @Override
            public void run() {
                if (url != null && url.startsWith("javascript:")) {
                    loadUrl(url, additionalHttpHeaders);
                } else {
                    onLoadUrlBefore(url);
                    loadUrl(url, additionalHttpHeaders);
                }
            }
        });
    }


    @Override
    public void reload() {
        onLoadUrlBefore(getUrl());
        super.reload();
    }

    @Override
    public void onResume() {
        super.onResume();
        notifyState("resume");
    }

    @Override
    public void onPause() {
        super.onPause();
        notifyState("pause");
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        notifyState("destroy");
    }

    @Override
    public void runOnMainThread(Runnable runnable) {
        if (Looper.getMainLooper() == Looper.myLooper()) {
            runnable.run();
            return;
        }
        mMainHandler.post(runnable);
    }

    @Override
    public void evaluateScript(String script) {
        L.i(TAG, "evaluateScript-->", script);
        if (Looper.getMainLooper() == Looper.myLooper()) {
            evaluateJavascript(script, null);
        } else {
            runOnMainThread(new Runnable() {
                @Override
                public void run() {
                    evaluateJavascript(script, null);
                }
            });
        }
    }

    @Override
    public DSPlugin findDSPlugin(String name) {
        if (TextUtils.equals(name, NAME_EXECUTE)) {
            return mDSExecute;
        } else if (TextUtils.equals(name, NAME_CHANNEL)) {
            return mDSChannel;
        }
        return mDSPluginMap.get(name);
    }

    @Override
    public boolean isDebug() {
        return DSWebView.DEBUG;
    }

    @Override
    public void executeScript(String script) {
        evaluateScript(script);
    }

    @Override
    public void closePage() {
        runOnMainThread(new Runnable() {
            @Override
            public void run() {
                if (onWindowListener == null || onWindowListener.onCloseWindow()) {
                    Context context = getContext();
                    if (context instanceof Activity) {
                        ((Activity) context).onBackPressed();
                    }
                }
            }
        });
    }

    private void internalCreatePlugin() {
        L.i(TAG, "internalCreatePlugin");
        createDSExecute();
        createDSChannel();
        if (mDSExecute != null) {
            addJavascriptInterface(mDSExecute, NAME_EXECUTE);
        }
        if (mDSChannel != null) {
            addJavascriptInterface(mDSChannel, NAME_CHANNEL);
        }
    }

    @Override
    public void onCreateDSPlugin() {
        Map<String, Class<? extends DSPlugin>> regeterMap = DSPluginProvider.getInstance().getRegeterMap();
        Set<String> nameSet = regeterMap.keySet();
        for (String name : nameSet) {
            Class<? extends DSPlugin> cls = regeterMap.get(name);
            try {
                if (cls != null) {
                    DSPlugin createPlugin = cls.newInstance();
                    createPlugin.setWebViewEvent(this);
                    mDSPluginMap.put(name, createPlugin);
                }
            } catch (Exception ex) {
                //TODO
                ex.printStackTrace();
            }
        }
        //
        final Context context = getContext();
        if (mDSExecute != null) {
            mDSExecute.onCreate(context);
        }
        if (mDSChannel != null) {
            mDSChannel.onCreate(context);
        }
        Collection<DSPlugin> plugins = mDSPluginMap.values();
        for (DSPlugin plugin : plugins) {
            if (plugin != null) {
                plugin.onCreate(context);
            }
        }
    }

    @Override
    public void addDSPlugin(String name, DSPlugin dsPlugin) {
        String setName = name;
        if (TextUtils.isEmpty(setName)) {
            setName = "";
        }
        dsPlugin.setWebViewEvent(this);
        dsPlugin.onCreate(getContext());
        mDSPluginMap.put(setName, dsPlugin);

    }

    @Override
    public void addDSPlugin(String name, Class<? extends DSPlugin> cls) {
        String setName = name;
        if (TextUtils.isEmpty(setName)) {
            setName = "";
        }
        try {
            if (cls != null) {
                DSPlugin dsPlugin = cls.newInstance();
                dsPlugin.setWebViewEvent(this);
                dsPlugin.onCreate(getContext());
                mDSPluginMap.put(setName, dsPlugin);
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void setAlertBoxBlock(boolean alertBoxBlock) {
        this.alertBoxBlock = alertBoxBlock;
    }

    @Override
    public boolean isAlertBoxBlock() {
        return alertBoxBlock;
    }

    public void disableJavascriptDialogBlock(boolean disable) {
        alertBoxBlock = !disable;
    }

    public void setOnWindowListener(OnWindowListener listener) {
        onWindowListener = listener;
    }

    /**
     * @param state : pause 、resume、destroy
     */
    protected void notifyState(String state) {
        if (!mDSPluginMap.isEmpty()) {
            Collection<DSPlugin> pluginList = mDSPluginMap.values();
            for (DSPlugin plugin : pluginList) {
                if (plugin != null) {
                    try {
                        switch (state) {
                            case "pause":
                                plugin.onPause();
                                break;
                            case "resume":
                                plugin.onResume();
                                break;
                            case "destroy":
                                plugin.onDestroy();
                                break;
                        }
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }
    }
}
